package Principal;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Principal {	
	
	//todos os tempos sao representados em micro segundos	 
	private static final BigDecimal ATRASO_HOST_1 = new BigDecimal(0.5F);
	private static final BigDecimal ATRASO_HOST_2 = new BigDecimal(0.4F);
	private static final BigDecimal ATRASO_HOST_3 = new BigDecimal(0.3F);
	private static final BigDecimal ATRASO_HOST_4 = new BigDecimal(0.2F);
	
	
	public static void main(String[] args) throws Exception {
		
		// Funções para recuperar via entrada manual do usuário os parâmetros de execução da simulação
		int cenario = obterInteiroConsole("Entre com o numero do cenario a ser simulado:");
		int numRodadas = obterInteiroConsole("Entre com o numero de rodadas da simulacao:");
		int tamanhoRodada = obterInteiroConsole("Entre com o tamanho da rodada:");		
		
		//Configura host de acordo com o cenario que 
		//o usuario da simulacao quer executar
		List<Host> hosts = recuperaCenario(cenario, numRodadas*tamanhoRodada);
		
		//inicializa os componentes necessarios para a simulacao			
		HistoriaSimulacao historiaSimulacao= new HistoriaSimulacao();
		Estatistica estatistica = new Estatistica(hosts, BigDecimal.ZERO, tamanhoRodada);
		
		// Inicializa as estatisticas
		initEstatisticasGerais(hosts, estatistica);
		
		// Cria os eventos que vao comecar a criar chegadas nos hosts
		for (Host host : hosts){
			Evento evento = new EventoNovaChegada(BigDecimal.ZERO, host, historiaSimulacao, estatistica, hosts);
			historiaSimulacao.adicionarEvento(evento);
		}
		
		long tempoInicio = System.currentTimeMillis();
		
		// Executa a Simulação
		rodada(historiaSimulacao, estatistica, tamanhoRodada);
		
		long tempoFim = System.currentTimeMillis();
		BigDecimal tempoSimulacao = new BigDecimal(tempoFim - tempoInicio);
		
		System.out.printf("\nTempo de Simulação = %s segundos\n\n", tempoSimulacao.multiply(new BigDecimal(0.001F).setScale(4, BigDecimal.ROUND_HALF_EVEN)).toString());
		
		
		// Calculo a Variancia para cada Host e para cada medida de interesse
		calcularEstatisticas(hosts, estatistica);
		
		//Imprimo as Estatisticas coletadas
		exibirEstatisticas(hosts, estatistica);		
		
		System.out.printf("Tecle qualquer tecla para sair.");
		BufferedReader is = new BufferedReader(new InputStreamReader(System.in));
		String line = is.readLine();
		
		System.exit(0) ;
	}

	//Escreve mensagem no console e obtem um valor tipo inteiro passado pelo cliente da aplicacao
	private static int obterInteiroConsole(String mensagem) throws Exception{
		int numero = 0;
		
		try
		{
			System.out.printf(mensagem);
			
			BufferedReader is = new BufferedReader(new InputStreamReader(System.in));
			String line = is.readLine();
			
			numero = Integer.parseInt(line);
		}
		catch(NumberFormatException ex){
			throw new Exception("O numero passado como parametro tem que ser um inteiro");
			
		} 
		catch (IOException e) {			
			e.printStackTrace();
		}
		
		return numero;
	}

	private static void calcularEstatisticas(List<Host> listaHosts, Estatistica estatistica) {
		
		// Loop para percorrer a lista de hosts
		for (Host host: listaHosts){
			
			// Só calculo a variância das medidas se o host estiver ativo (ou seja gerando tráfego)
			if (host.isAtivo()){
			
				// recupero as estatísticas da rodada
				EstatisticaHost estatisticaHost = estatistica.getEstatisticasGerais().get(host);			
				
				// TAP
				estatisticaHost.varianciaTempoAcessoQuadro = Util.calcularVariancia(estatisticaHost.somatorioTempoAcessoQuadro, estatisticaHost.somatorioTempoAcessoQuadro2, estatisticaHost.numeroQuadrosEnviados); 		
				
				// TAM
				estatisticaHost.varianciaTempoAcessoMsg = Util.calcularVariancia(estatisticaHost.somatorioTempoAcessoMsg, estatisticaHost.somatorioTempoAcessoMsg2, estatisticaHost.numeroMsgEnviadas); 		
								
				// NCm
				estatisticaHost.varianciaNCm = Util.calcularVariancia(estatisticaHost.somatorioNCm, estatisticaHost.somatorioNCm2, estatisticaHost.numeroColisoesPorMensagem); 		
				
				// Utilizacao
				estatisticaHost.varianciaTempoOcupado = Util.calcularVariancia(estatisticaHost.somatorioTempoOcupado, estatisticaHost.somatorioTempoOcupado2, estatisticaHost.totalTempoOcupado); 		
				
				// Vazao
				estatisticaHost.varianciaVazao = Util.calcularVariancia(estatisticaHost.somatorioVazao, estatisticaHost.somatorioVazao2, estatisticaHost.numeroMsgEnviadas); 		
			}
		}
	}

	// Método que incializa as variáveis de cálculo de estatística
	private static void initEstatisticasGerais(List<Host> listaHost, Estatistica estatistica) {
		for (Host host: listaHost) {
			estatistica.getEstatisticasGerais().put(host, new EstatisticaHost(host, BigDecimal.ZERO));
		}
		
	}

	private static void rodada(HistoriaSimulacao historiaSimulacao, Estatistica estatistica, int tamanhoRodada) {
		BigDecimal tempoAtual = BigDecimal.ZERO;
		
		// Fico em Loop até terminar a fila de eventos
		while (!historiaSimulacao.isFilaEventosVazia()){
			
			// recupero o primeiro evento da fila
			Evento eventoAtual = historiaSimulacao.getPrimeiroEvento();
			
			//Remove o evento da fila de eventos antes do seu tratamento
			historiaSimulacao.removePrimeiroEventoFila();
			
			//Avanca o tempo atual para o tempo do evento a ser tratado
			tempoAtual = eventoAtual.getTempoExecucao();
			estatistica.setTempoSimulacao(tempoAtual);			
			//Trata o primeiro evento da fila de eventos
			eventoAtual.executar();
						
			// Veirifico se cheguei ao fim de uma rodada p/ algum host
			//verificaFimRodada(estatistica, tamanhoRodada, tempoAtual);
			
			// Chama o Garbage Collector para liberação de Memória
			System.gc();			
		}
		
		System.out.println("Fim da rodada no tempo =" + tempoAtual.setScale(4, BigDecimal.ROUND_HALF_EVEN).toString() +"!!!!");

	}
	
	/*
	// Método responsável por verificar se foi atingido o fim de uma rodada de simulação
	private static void verificaFimRodada(Estatistica estatistica, int tamanhoRodada, BigDecimal tempoAtual) {
		if(estatistica.getNumeroMsgEnviadasSistema() != 0 && estatistica.getNumeroMsgEnviadasSistema()%tamanhoRodada == 0){
			for (Host host : estatistica.getHosts()) {
				//if(estatistica.listaEstatisticaHost.get(host).numeroMsgEnviadas > 0 && estatistica.listaEstatisticaHost.get(host).numeroMsgEnviadas%tamanhoRodada == 0 ){
				if(host.isAtivo()){
					calculaEstatistica(estatistica.listaEstatisticaHost.get(host), tempoAtual);
					estatistica.listaEstatisticaHost.put(host, new EstatisticaHost(host, tempoAtual));
				}
				//}
			}
		}
	}

	private static void calculaEstatistica(EstatisticaHost estatisticaHost, BigDecimal tempoAtual) {
		EstatisticaHost estatisticaHostGeral = estatisticas.get(estatisticaHost.host);
		
		// Incremento o Somatório para cada uma das medidas
		estatisticaHostGeral.numeroQuadrosEnviados++;
		estatisticaHostGeral.numeroMsgEnviadas++;
		estatisticaHostGeral.numeroColisoesPorMensagem++;
		estatisticaHostGeral.totalTempoOcupado++;
		
		estatisticaHostGeral.somatorioTempoAcessoMsg = estatisticaHostGeral.somatorioTempoAcessoMsg.add(estatisticaHost.getMediaTempoAcessoMsg());
		estatisticaHostGeral.somatorioTempoAcessoMsg2 = estatisticaHostGeral.somatorioTempoAcessoMsg2.add(estatisticaHost.getMediaTempoAcessoMsg().pow(2));
		
		estatisticaHostGeral.somatorioTempoAcessoQuadro = estatisticaHostGeral.somatorioTempoAcessoQuadro.add(estatisticaHost.getMediaTempoAcessoQuadro());
		estatisticaHostGeral.somatorioTempoAcessoQuadro2 = estatisticaHostGeral.somatorioTempoAcessoQuadro2.add(estatisticaHost.getMediaTempoAcessoQuadro().pow(2));
		
		estatisticaHostGeral.somatorioNCm = estatisticaHostGeral.somatorioNCm.add(estatisticaHost.getMediaColisaoPorMensagem());
		estatisticaHostGeral.somatorioNCm2 = estatisticaHostGeral.somatorioNCm2.add(estatisticaHost.getMediaColisaoPorMensagem().pow(2));
		
		estatisticaHostGeral.somatorioTempoOcupado = estatisticaHostGeral.somatorioTempoOcupado.add(estatisticaHost.getUtilizacao(tempoAtual));
		estatisticaHostGeral.somatorioTempoOcupado2 = estatisticaHostGeral.somatorioTempoOcupado2.add(estatisticaHost.getUtilizacao(tempoAtual).pow(2));
				
		estatisticaHostGeral.somatorioVazao = estatisticaHostGeral.somatorioVazao.add(estatisticaHost.getVazao(tempoAtual));
		estatisticaHostGeral.somatorioVazao2 = estatisticaHostGeral.somatorioVazao2.add(estatisticaHost.getVazao(tempoAtual).pow(2));
	
	}
	*/

	private static void exibirEstatisticas(List<Host> listaHosts, Estatistica estatistica ) {
		
		System.out.printf("\nEstatísticas coletadas:\n\n");
		
		for (Host host: listaHosts){
			EstatisticaHost estatisticaHost = estatistica.getEstatisticasGerais().get(host);
		
			
			if (estatisticaHost.host.isAtivo()){
				System.out.printf("Estatísticas para o Host: %s\n", estatisticaHost.host.toString());
				
				// Imprime TAP
				BigDecimal media = estatisticaHost.getMediaTempoAcessoQuadro();
				BigDecimal intervalo = estatisticaHost.getIntervaloConfiancaTempoAcessoQuadro();
				System.out.printf("\tTempo medio de acesso do quadro(E[Tap]): %s \n", media.toString());
				System.out.printf("\tIntervalo de confianca: [%s,%s]\n",media.subtract(intervalo).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString(), media.add(intervalo).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
				System.out.printf("\t10 %s da Média = %s\n", "%", media.multiply(new BigDecimal(0.1F)).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
				System.out.printf("\tU(alpha) - L(alpha) = %s\n\n", media.add(intervalo).subtract(media.subtract(intervalo)).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
				
				// Imprime TAM
				media = estatisticaHost.getMediaTempoAcessoMsg();
				intervalo = estatisticaHost.getIntervaloConfiancaTempoAcessoMsg();
				System.out.printf("\tTempo medio de acesso da Mensagem(E[Tam]): %s \n", media.toString());
				System.out.printf("\tIntervalo de confianca: [%s,%s]\n",media.subtract(intervalo).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString(), media.add(intervalo).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
				System.out.printf("\t10 %s da Média = %s\n", "%", media.multiply(new BigDecimal(0.1F)).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
				System.out.printf("\tU(alpha) - L(alpha) = %s\n\n", media.add(intervalo).subtract(media.subtract(intervalo)).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
			
				
				// Imprime NCm
				media = estatisticaHost.getMediaColisaoPorMensagem();
				intervalo = estatisticaHost.getIntervaloConfiancaColisaoPorMensagem();
				System.out.printf("\tNumero medio de colisoes por quadro(E[NCm]): %s \n", media.toString());
				System.out.printf("\tIntervalo de confianca: [%s,%s]\n",media.subtract(intervalo).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString(), media.add(intervalo).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
				System.out.printf("\t10 %s da Média = %s\n", "%", media.multiply(new BigDecimal(0.1F)).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
				System.out.printf("\tU(alpha) - L(alpha) = %s\n\n", media.add(intervalo).subtract(media.subtract(intervalo)).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
				
				// Imprime Vazao
				media = estatisticaHost.getMediaVazao();
				intervalo = estatisticaHost.getIntervaloConfiancaVazao();
				System.out.printf("\tVazao: %s quadros/s\n", media.toString());
				System.out.printf("\tIntervalo de confianca: [%s,%s]\n",media.subtract(intervalo).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString(), media.add(intervalo).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
				System.out.printf("\t10 %s da Média = %s\n", "%", media.multiply(new BigDecimal(0.1F)).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
				System.out.printf("\tU(alpha) - L(alpha) = %s\n\n", media.add(intervalo).subtract(media.subtract(intervalo)).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
				
				// Se for o Host 1 Imprime Utilização
				if (estatisticaHost.host.toString() == "1"){
					media = estatisticaHost.getMediaTempoOcupado();
					intervalo = estatisticaHost.getIntervaloConfiancaTempoOcupado();
					System.out.printf("\tUtilização (porcentagem): %s\n", media.toString());
					System.out.printf("\tIntervalo de confianca: [%s,%s]\n",media.subtract(intervalo).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString(), media.add(intervalo).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
					System.out.printf("\t10 %s da Média = %s\n", "%", media.multiply(new BigDecimal(0.1F)).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
					System.out.printf("\tU(alpha) - L(alpha) = %s\n\n", media.add(intervalo).subtract(media.subtract(intervalo)).setScale(4, BigDecimal.ROUND_HALF_EVEN).toString());
				}
			}
		}
	}

	// Recupero o cenário entrado pelo usuário e em seguida inicializo as variáveis pertinentes em cada host
	public static List<Host> recuperaCenario(int cenario, int numRodadas) throws IOException{
		
		List<Host> listaHost = new ArrayList<Host>();
		
		Host host1 = null; 
		Host host2 = null;					
		Host host3 = null; 					
		Host host4 = null;
		
		int sementeHost1 = 1;
		int sementeHost2 = 19580427;
		int sementeHost3 = 5000;
		int sementeHost4 = 2345677;
		
		switch (cenario) 
		{
			case 1:
				System.out.printf("\nComeçando caso de teste 1.\n");
				host1 = new Host("1",ATRASO_HOST_1, new BigDecimal(80000), 40, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost1);
				host2 = new Host("2", ATRASO_HOST_2, new BigDecimal(80000), 40, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas,sementeHost2);					
				host3 = new Host("3", ATRASO_HOST_3, BigDecimal.ZERO, 0, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost3);					
				host4 = new Host("4", ATRASO_HOST_4, BigDecimal.ZERO, 0, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost4);
				break;
			case 2:
				System.out.printf("\nComeçando caso de teste 2.\n");
				host1 = new Host("1", ATRASO_HOST_1, new BigDecimal(80000), 40, Host.DistribuicaoIntervalo.EXPONENCIAL, numRodadas, sementeHost1);
				host2 = new Host("2", ATRASO_HOST_2, new BigDecimal(80000), 40, Host.DistribuicaoIntervalo.EXPONENCIAL, numRodadas, sementeHost2);
				host3 = new Host("3", ATRASO_HOST_3, BigDecimal.ZERO, 0, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost3);
				host4 = new Host("4", ATRASO_HOST_4, BigDecimal.ZERO, 0, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost4);
				break;
			case 3:
				System.out.printf("\nComeçando caso de teste 3.\n");
				host1 = new Host("1", ATRASO_HOST_1, new BigDecimal(80000), 40, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost1);
				host2 = new Host("2", ATRASO_HOST_2, new BigDecimal(16000), 1, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost2);
				host3 = new Host("3", ATRASO_HOST_3, new BigDecimal(16000), 1, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost3);
				host4 = new Host("4", ATRASO_HOST_4, new BigDecimal(16000), 1, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost4);
				break;
			case 4:
				System.out.printf("\nComeçando caso de teste 4.\n");
				host1 = new Host("1", ATRASO_HOST_1, new BigDecimal(80000), 40, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost1);
				host2 = new Host("2",  ATRASO_HOST_2, new BigDecimal(16000), 1, Host.DistribuicaoIntervalo.EXPONENCIAL, numRodadas, sementeHost2);
				host3 = new Host("3",  ATRASO_HOST_3, new BigDecimal(16000), 1, Host.DistribuicaoIntervalo.EXPONENCIAL, numRodadas, sementeHost3);
				host4 = new Host("4",  ATRASO_HOST_4, new BigDecimal(16000), 1, Host.DistribuicaoIntervalo.EXPONENCIAL, numRodadas, sementeHost4);
				break;
			case 5:  // TESTE
				System.out.printf("\nCenario de Teste 1!\n");
				host1 = new Host("1",  ATRASO_HOST_1, new BigDecimal(1000), 1  , Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost1);
				host2 = new Host("2",  ATRASO_HOST_2, BigDecimal.ZERO, 0, Host.DistribuicaoIntervalo.EXPONENCIAL, numRodadas, sementeHost2);
				host3 = new Host("3",  ATRASO_HOST_3, BigDecimal.ZERO, 0, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost3);
				host4 = new Host("4",  ATRASO_HOST_4, BigDecimal.ZERO, 0, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost4);
				break;			
			case 6:  // TESTE
				System.out.printf("\nCenario de Teste 2!\n");
				host1 = new Host("1",  ATRASO_HOST_1, new BigDecimal(1000), 2  , Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost1);
				host2 = new Host("2",  ATRASO_HOST_2, BigDecimal.ZERO, 0, Host.DistribuicaoIntervalo.EXPONENCIAL, numRodadas, sementeHost2);
				host3 = new Host("3",  ATRASO_HOST_3, BigDecimal.ZERO, 0, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost3);
				host4 = new Host("4",  ATRASO_HOST_4, BigDecimal.ZERO, 0, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost4);
				break;
			case 7:
				System.out.printf("\nCenario de Teste 3\n");
				host1 = new Host("1",  ATRASO_HOST_1, new BigDecimal(16000), 2, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost1);
				host2 = new Host("2",  ATRASO_HOST_2, new BigDecimal(16000), 2, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost2);					
				host3 = new Host("3",  ATRASO_HOST_3, BigDecimal.ZERO, 0, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost3);					
				host4 = new Host("4",  ATRASO_HOST_4, BigDecimal.ZERO, 0, Host.DistribuicaoIntervalo.DETERMINISTICO, numRodadas, sementeHost4);
				break;
			default:
				System.out.printf("Erro Nenhum cenario valido escolhido!");
				int termino = System.in.read();
				System.exit(termino);					
		}	
				
		listaHost.add(host1);
		listaHost.add(host2);
		listaHost.add(host3);
		listaHost.add(host4);

		return listaHost;
	}
}
